package jssdk

import (
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/flaravel/quanlaolao/pkg/config"
	"github.com/flaravel/quanlaolao/pkg/types"
	"github.com/syyongx/php2go"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"sync"
	"time"
)

type WxAccessToken struct {
	Access_token string `json:"access_token"`
	Expires_in   int    `json:"expires_in"`
	Errcode      int    `json:"errcode"`
	Errmsg       string `json:"errmsg"`
}
type WxJsApiTicket struct {
	Ticket     string `json:"ticket"`
	Expires_in int    `json:"expires_in"`
	Errcode    int    `json:"errcode"`
	Errmsg     string `json:"errmsg"`
}
type WxSignature struct {
	Noncestr  string `json:"noncestr"`
	Timestamp string `json:"timestamp"`
	Url       string `json:"url"`
	Signature string `json:"signature"`
	AppID     string `json:"appId"`
}

// 数据缓存处理
type Item struct {
	Value      string
	CreateTime time.Time
	LifeTime   time.Duration
}

type MemoryCache struct {
	sync.RWMutex
	Items map[string]*Item
}
var (
	MemoryCacheVar  MemoryCache
)

func New(path string) (string,error)  {
	var (
		AppID           string = config.GetString("wechat.wechat_app_id")
		AppSecret       string = config.GetString("wechat.wechat_app_secret")
		AccessTokenHost string = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + AppID + "&secret=" + AppSecret
		JsAPITicketHost string = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
	)
	var (
		noncestr, jsapi_ticket, timestamp, url, signature, signatureStr, access_token string
		wxAccessToken                                                                 WxAccessToken
		wxJsApiTicket                                                                 WxJsApiTicket
		wxSignature                                                                   WxSignature
	)
	url = path
	noncestr = types.RandToken(16)
	timestamp = strconv.FormatInt(time.Now().Unix(), 10)

	//获取access_token，如果缓存中有，则直接取出数据使用；否则重新调用微信端接口获取
	client := &http.Client{}
	if MemoryCacheVar.Get("access_token") == nil {
		request, _ := http.NewRequest("GET", AccessTokenHost, nil)
		response, _ := client.Do(request)
		defer response.Body.Close()
		body, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return "", err
		}
		err = json.Unmarshal(body, &wxAccessToken)
		if err != nil {
			return "", err
		}
		if wxAccessToken.Errcode == 0 {
			access_token = wxAccessToken.Access_token
		} else {
			return "", errors.New(wxAccessToken.Errmsg)
		}
		MemoryCacheVar.Put("access_token", access_token, time.Duration(wxAccessToken.Expires_in)*time.Second)

		//获取 jsapi_ticket
		requestJs, _ := http.NewRequest("GET", JsAPITicketHost+"?access_token="+access_token+"&type=jsapi", nil)
		responseJs, _ := client.Do(requestJs)
		defer responseJs.Body.Close()
		bodyJs, err := ioutil.ReadAll(responseJs.Body)
		if err != nil {
			return "", err
		}
		err = json.Unmarshal(bodyJs, &wxJsApiTicket)
		if err != nil {
			return "", err
		}
		if wxJsApiTicket.Errcode == 0 {
			jsapi_ticket = wxJsApiTicket.Ticket
		} else {
			return "", errors.New(wxJsApiTicket.Errmsg)
		}
		MemoryCacheVar.Put("jsapi_ticket", jsapi_ticket, time.Duration(wxJsApiTicket.Expires_in)*time.Second)
	} else {
		//缓存中存在access_token，直接读取
		access_token = MemoryCacheVar.Get("access_token").(*Item).Value
		jsapi_ticket = MemoryCacheVar.Get("jsapi_ticket").(*Item).Value
	}
	fmt.Println("access_token:",access_token)
	fmt.Println("jsapi_ticket:",jsapi_ticket)

	// 获取 signature
	signatureStr = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url=" + url
	signature = GetSha1(signatureStr)

	wxSignature.Url = url
	wxSignature.Noncestr = noncestr
	wxSignature.Timestamp = timestamp
	wxSignature.Signature = signature
	wxSignature.AppID = AppID

	// 返回前端需要的数据
	signs, _ := php2go.JSONEncode(wxSignature)
	return string(signs), nil
}

//SHA1加密
func GetSha1(data string) string {
	t := sha1.New()
	io.WriteString(t, data)
	return fmt.Sprintf("%x", t.Sum(nil))
}

func (mc *MemoryCache) Put(key string, value string, lifeTime time.Duration) {
	mc.Lock()
	defer mc.Unlock()
	mc.Items = map[string]*Item{}
	mc.Items[key] = &Item{
		LifeTime:   lifeTime,
		Value:      value,
		CreateTime: time.Now(),
	}
}

func (mc *MemoryCache) Get(key string) interface{} {
	mc.RLock()
	defer mc.RUnlock()
	if e, ok := mc.Items[key]; ok {
		if !e.isExpire() {
			return e
		} else {
			delete(mc.Items, key)
		}
	}
	return nil
}

func (e *Item) isExpire() bool {
	if e.LifeTime == 0 {
		return false
	}
	//根据创建时间和生命周期判断元素是否失效
	return time.Now().Sub(e.CreateTime) > e.LifeTime
}